package com.googlecode.vijayan.dogen;

import java.io.File;
import java.io.FileWriter;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;

/**
 * 
 * @author Vijayan Srinivasan
 * @since Sep 4, 2013 8:23:40 PM
 * 
 */
@Mojo(name = "generate", defaultPhase = LifecyclePhase.GENERATE_SOURCES)
public class DataObjectGenerator extends AbstractMojo {

	@Parameter(required = true)
	private String driver;

	@Parameter(required = true)
	private String url;

	@Parameter
	private String user;

	@Parameter
	private String password;

	@Parameter
	private String packageName;

	@Parameter
	private String schema = "";

	@Parameter
	private String classNameSuffix;

	@Component
	private MavenProject project;
	private Connection connection;

	public DataObjectGenerator() {
		this.classNameSuffix = "";
	}

	private String getGenSrcFolderName() {
		return this.project.getModel().getBuild().getDirectory()
				+ File.separator + "generated-sources";
	}

	public Database getDatabase() {
		try {
			DatabaseMetaData metaData = this.connection.getMetaData();
			Database database = new Database();
			database.setProductName(metaData.getDatabaseProductName());
			database.setProductVersion(metaData.getDatabaseProductVersion());

			ResultSet resultSet = metaData.getTables(null,
					schema.toUpperCase(), "%", null);
			while (resultSet.next()) {
				String tableName = resultSet.getString("TABLE_NAME");
				Table table = getTable(tableName);
				database.getTables().add(table);
			}

			close(resultSet);
			return database;
		} catch (SQLException e) {
			getLog().error(e.getMessage());

		} finally {
			close(this.connection);
		}
		return null;
	}

	public void execute() throws MojoExecutionException {
		init();
		Database database = getDatabase();
		getLog().info(
				"Conneted to Database: " + database.getProductName()
						+ " Version: " + database.getProductVersion());

		this.project.addCompileSourceRoot(getGenSrcFolderName());
		List<Table> tables = database.getTables();

		for (Table table : tables) {
			
			if(table.hasPk()){
				getLog().info("Creating class for " + table.getName());
	
				VelocityEngine ve = new VelocityEngine();
				ve.setProperty("resource.loader", "classpath");
				ve.setProperty("classpath.resource.loader.class",
						ClasspathResourceLoader.class.getName());
	
				ve.init();
	
				VelocityContext context = new VelocityContext();
				context.put("table", table);
				context.put("suffix", this.classNameSuffix);
				context.put("date", new Date());
				context.put("packageName", this.packageName);
				
				createDto(table, ve, context);
				createDao(table, ve, context);
			}else{
				getLog().error("Table " + table.getName() + " has no primary key and hence skipped for code generation.");
			}
		}

		close(this.connection);
	}

	private void createDao(Table table, VelocityEngine ve,
			VelocityContext context) {
		String className = table.getEntityName();
		String folderName = getGenSrcFolderName();
		this.project.addCompileSourceRoot(folderName);

		String fileName = File.separator + className + "Repository.java";
		if (this.packageName == null) {
			fileName = folderName + fileName;
		} else {
			String fullPackgaeName = this.packageName + "." + "repo";
			String subFolder = fullPackgaeName.replace('.', File.separatorChar);
			fileName = folderName + File.separator + subFolder + fileName;
		}
		File file = new File(fileName);
		File parent = file.getParentFile();
		parent.mkdirs();
		getLog().info("Creating " + file.getAbsolutePath());
		try {
			FileWriter writer = new FileWriter(file);
			Template daoTemplate = ve.getTemplate("dao.vm");

			daoTemplate.merge(context, writer);
			writer.flush();
			writer.close();
		} catch (Exception e) {
			getLog().error("Error while creating DTO for " + table.getName(), e);
		}
	}

	private void createDto(Table table, VelocityEngine ve,
			VelocityContext context) {
		String className = table.getEntityName();
		if (this.classNameSuffix != null) {
			className = className + this.classNameSuffix;
		}
		String folderName = getGenSrcFolderName();
		this.project.addCompileSourceRoot(folderName);

		String fileName = File.separator + className + ".java";
		if (this.packageName == null) {
			fileName = folderName + fileName;
		} else {
			String fullPackgaeName = this.packageName + "." + "dto";
			String subFolder = fullPackgaeName.replace('.', File.separatorChar);
			fileName = folderName + File.separator + subFolder + fileName;
		}
		File file = new File(fileName);
		File parent = file.getParentFile();
		parent.mkdirs();
		getLog().info("Creating " + file.getAbsolutePath());
		try {
			FileWriter writer = new FileWriter(file);
			Template dtoTemplate = ve.getTemplate("dto.vm");

			dtoTemplate.merge(context, writer);
			writer.flush();
			writer.close();
		} catch (Exception e) {
			getLog().error("Error while creating DTO for " + table.getName(), e);
		}
	}

	public void init() throws MojoExecutionException {
		try {
			Class.forName(this.driver);
		} catch (ClassNotFoundException e) {
			throw new MojoExecutionException(e.getMessage()
					+ " is missing in classpath");

		}

		if ((this.user != null) && (this.password != null))
			try {
				this.connection = DriverManager.getConnection(this.url,
						this.user, this.password);
			} catch (SQLException e) {
				throw new MojoExecutionException(e.getMessage());
			}
		else
			try {
				this.connection = DriverManager.getConnection(this.url);
			} catch (SQLException e) {
				throw new MojoExecutionException(e.getMessage());
			}
	}

	public Table getTable(String tableName) {
		Table table = new Table();
		table.setName(tableName);
		List<Column> columns = getColumns(tableName);
		String primaryKey = getPrimaryKey(tableName);
		table.setPrimaryKey(primaryKey);
		table.setColumns(columns);
		return table;
	}

	private String getPrimaryKey(String tableName) {
		String primryKey = null;
		try {
			DatabaseMetaData metaData = this.connection.getMetaData();
			ResultSet resultSet = metaData
					.getPrimaryKeys(null, null, tableName);

			if (resultSet.next()) {
				primryKey = resultSet.getString(4);
			}
			close(resultSet);
		} catch (SQLException e) {
			getLog().error(
					"Error while collecting primary key: " + e.getMessage());
		}

		return primryKey;
	}

	private List<Column> getColumns(String tableName) {
		List<Column> columns = new ArrayList<Column>();
		try {
			String sql = "select * from " + tableName + " where 1=2";
			Statement statement = this.connection.createStatement();
			ResultSet resultSet = statement.executeQuery(sql);
			ResultSetMetaData metaData = resultSet.getMetaData();
			for (int i = 1; i <= metaData.getColumnCount(); ++i) {
				String columnName = metaData.getColumnName(i);
				String className = metaData.getColumnClassName(i);
				Column column = new Column();
				column.setClassName(className);
				column.setName(columnName);
				columns.add(column);
			}
			close(resultSet);
		} catch (SQLException e) {
			getLog().error(
					"Error while collecting column names: " + e.getMessage());
		}

		return columns;
	}

	private void close(ResultSet resultSet) {
		if (resultSet == null)
			return;
		try {
			resultSet.close();
		} catch (SQLException e) {
			getLog().error("Error while closing resultSet: " + e.getMessage());
		}
	}

	private void close(Connection connection) {
		if (connection == null)
			return;
		try {
			connection.close();
		} catch (SQLException e) {
			getLog().error("Error while closing connection: " + e.getMessage());
		}
	}

	public String getDriver() {
		return this.driver;
	}

	public void setDriver(String driver) {
		this.driver = driver;
	}

	public String getUrl() {
		return this.url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public String getUser() {
		return this.user;
	}

	public void setUser(String user) {
		this.user = user;
	}

	public String getPassword() {
		return this.password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getPackageName() {
		return this.packageName;
	}

	public void setPackageName(String packageName) {
		this.packageName = packageName;
	}

	public String getClassNameSuffix() {
		return this.classNameSuffix;
	}

	public void setClassNameSuffix(String classNameSuffix) {
		this.classNameSuffix = classNameSuffix;
	}

	public MavenProject getProject() {
		return this.project;
	}

	public void setProject(MavenProject project) {
		this.project = project;
	}

	public String getSchema() {
		return schema;
	}

	public void setSchema(String schema) {
		this.schema = schema;
	}
}
